<#ftl>
<#--
        Copyright 2013 The Android Open Source Project

        Licensed under the Apache License, Version 2.0 (the "License");
        you may not use this file except in compliance with the License.
        You may obtain a copy of the License at

        http://www.apache.org/licenses/LICENSE-2.0

        Unless required by applicable law or agreed to in writing, software
        distributed under the License is distributed on an "AS IS" BASIS,
        WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
        See the License for the specific language governing permissions and
        limitations under the License.
-->

package ${sample.package}.cardstream;

import android.animation.Animator;
import android.animation.LayoutTransition;
import android.animation.ObjectAnimator;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.TypedArray;
import android.graphics.Rect;
import android.os.Build;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewGroup;
import android.view.ViewParent;
import android.widget.LinearLayout;
import android.widget.ScrollView;

import com.example.android.common.logger.Log;
import ${sample.package}.R;

import java.util.ArrayList;

/**
 * A Layout that contains a stream of card views.
 */
public class CardStreamLinearLayout extends LinearLayout {

    public static final int ANIMATION_SPEED_SLOW = 1001;
    public static final int ANIMATION_SPEED_NORMAL = 1002;
    public static final int ANIMATION_SPEED_FAST = 1003;

    private static final String TAG = "CardStreamLinearLayout";
    private final ArrayList<View> mFixedViewList = new ArrayList<View>();
    private final Rect mChildRect = new Rect();
    private CardStreamAnimator mAnimators;
    private OnDissmissListener mDismissListener = null;
    private boolean mLayouted = false;
    private boolean mSwiping = false;
    private String mFirstVisibleCardTag = null;
    private boolean mShowInitialAnimation = false;

    /**
     * Handle touch events to fade/move dragged items as they are swiped out
     */
    private OnTouchListener mTouchListener = new OnTouchListener() {

        private float mDownX;
        private float mDownY;

        @Override
        public boolean onTouch(final View v, MotionEvent event) {

            switch (event.getAction()) {
                case MotionEvent.ACTION_DOWN:
                    mDownX = event.getX();
                    mDownY = event.getY();
                    break;
                case MotionEvent.ACTION_CANCEL:
                    resetAnimatedView(v);
                    mSwiping = false;
                    mDownX = 0.f;
                    mDownY = 0.f;
                    break;
                case MotionEvent.ACTION_MOVE: {

                    float x = event.getX() + v.getTranslationX();
                    float y = event.getY() + v.getTranslationY();

                    mDownX = mDownX == 0.f ? x : mDownX;
                    mDownY = mDownY == 0.f ? x : mDownY;

                    float deltaX = x - mDownX;
                    float deltaY = y - mDownY;

                    if (!mSwiping && isSwiping(deltaX, deltaY)) {
                        mSwiping = true;
                        v.getParent().requestDisallowInterceptTouchEvent(true);
                    } else {
                        swipeView(v, deltaX, deltaY);
                    }
                }
                break;
                case MotionEvent.ACTION_UP: {
                    // User let go - figure out whether to animate the view out, or back into place
                    if (mSwiping) {
                        float x = event.getX() + v.getTranslationX();
                        float y = event.getY() + v.getTranslationY();

                        float deltaX = x - mDownX;
                        float deltaY = y - mDownX;
                        float deltaXAbs = Math.abs(deltaX);

                        // User let go - figure out whether to animate the view out, or back into place
                        boolean remove = deltaXAbs > v.getWidth() / 4 && !isFixedView(v);
                        if( remove )
                            handleViewSwipingOut(v, deltaX, deltaY);
                        else
                            handleViewSwipingIn(v, deltaX, deltaY);
                    }
                    mDownX = 0.f;
                    mDownY = 0.f;
                    mSwiping = false;
                }
                break;
                default:
                    return false;
            }
            return false;
        }
    };
    private int mSwipeSlop = -1;
    /**
     * Handle end-transition animation event of each child and launch a following animation.
     */
    private LayoutTransition.TransitionListener mTransitionListener
            = new LayoutTransition.TransitionListener() {

        @Override
        public void startTransition(LayoutTransition transition, ViewGroup container, View
                view, int transitionType) {
            Log.d(TAG, "Start LayoutTransition animation:" + transitionType);
        }

        @Override
        public void endTransition(LayoutTransition transition, ViewGroup container,
                                  final View view, int transitionType) {

            Log.d(TAG, "End LayoutTransition animation:" + transitionType);
            if (transitionType == LayoutTransition.APPEARING) {
                final View area = view.findViewById(R.id.card_actionarea);
                if (area != null) {
                    runShowActionAreaAnimation(container, area);
                }
            }
        }
    };
    /**
     * Handle a hierarchy change event
     * when a new child is added, scroll to bottom and hide action area..
     */
    private OnHierarchyChangeListener mOnHierarchyChangeListener
            = new OnHierarchyChangeListener() {
        @Override
        public void onChildViewAdded(final View parent, final View child) {

            Log.d(TAG, "child is added: " + child);

            ViewParent scrollView = parent.getParent();
            if (scrollView != null && scrollView instanceof ScrollView) {
                ((ScrollView) scrollView).fullScroll(FOCUS_DOWN);
            }

            if (getLayoutTransition() != null) {
                View view = child.findViewById(R.id.card_actionarea);
                if (view != null)
                    view.setAlpha(0.f);
            }
        }

        @Override
        public void onChildViewRemoved(View parent, View child) {
            Log.d(TAG, "child is removed: " + child);
            mFixedViewList.remove(child);
        }
    };
    private int mLastDownX;

    public CardStreamLinearLayout(Context context) {
        super(context);
        initialize(null, 0);
    }

    public CardStreamLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialize(attrs, 0);
    }

    @SuppressLint("NewApi")
    public CardStreamLinearLayout(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        initialize(attrs, defStyle);
    }

    /**
     * add a card view w/ canDismiss flag.
     *
     * @param cardView   a card view
     * @param canDismiss flag to indicate this card is dismissible or not.
     */
    public void addCard(View cardView, boolean canDismiss) {
        if (cardView.getParent() == null) {
            initCard(cardView, canDismiss);

            ViewGroup.LayoutParams param = cardView.getLayoutParams();
            if(param == null)
                param = generateDefaultLayoutParams();

            super.addView(cardView, -1, param);
        }
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        if (child.getParent() == null) {
            initCard(child, true);
            super.addView(child, index, params);
        }
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        super.onLayout(changed, l, t, r, b);
        Log.d(TAG, "onLayout: " + changed);

        if( changed && !mLayouted ){
            mLayouted = true;

            ObjectAnimator animator;
            LayoutTransition layoutTransition = new LayoutTransition();

            animator = mAnimators.getDisappearingAnimator(getContext());
            layoutTransition.setAnimator(LayoutTransition.DISAPPEARING, animator);

            animator = mAnimators.getAppearingAnimator(getContext());
            layoutTransition.setAnimator(LayoutTransition.APPEARING, animator);

            layoutTransition.addTransitionListener(mTransitionListener);

            if( animator != null )
                layoutTransition.setDuration(animator.getDuration());

            setLayoutTransition(layoutTransition);

            if( mShowInitialAnimation )
                runInitialAnimations();

            if (mFirstVisibleCardTag != null) {
                scrollToCard(mFirstVisibleCardTag);
                mFirstVisibleCardTag = null;
            }
        }
    }

    /**
     * Check whether a user moved enough distance to start a swipe action or not.
     *
     * @param deltaX
     * @param deltaY
     * @return true if a user is swiping.
     */
    protected boolean isSwiping(float deltaX, float deltaY) {

        if (mSwipeSlop < 0) {
            //get swipping slop from ViewConfiguration;
            mSwipeSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
        }

        boolean swipping = false;
        float absDeltaX = Math.abs(deltaX);

        if( absDeltaX > mSwipeSlop )
            return true;

        return swipping;
    }

    /**
     * Swipe a view by moving distance
     *
     * @param child a target view
     * @param deltaX x moving distance by x-axis.
     * @param deltaY y moving distance by y-axis.
     */
    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    protected void swipeView(View child, float deltaX, float deltaY) {
        if (isFixedView(child)){
            deltaX = deltaX / 4;
        }

        float deltaXAbs = Math.abs(deltaX);
        float fractionCovered = deltaXAbs / (float) child.getWidth();

        child.setTranslationX(deltaX);
        child.setAlpha(1.f - fractionCovered);

        if (deltaX > 0)
            child.setRotationY(-15.f * fractionCovered);
        else
            child.setRotationY(15.f * fractionCovered);
    }

    protected void notifyOnDismissEvent( View child ){
        if( child == null || mDismissListener == null )
            return;

        mDismissListener.onDismiss((String) child.getTag());
    }

    /**
     * get the tag of the first visible child in this layout
     *
     * @return tag of the first visible child or null
     */
    public String getFirstVisibleCardTag() {

        final int count = getChildCount();

        if (count == 0)
            return null;

        for (int index = 0; index < count; ++index) {
            //check the position of each view.
            View child = getChildAt(index);
            if (child.getGlobalVisibleRect(mChildRect) == true)
                return (String) child.getTag();
        }

        return null;
    }

    /**
     * Set the first visible card of this linear layout.
     *
     * @param tag tag of a card which should already added to this layout.
     */
    public void setFirstVisibleCard(String tag) {
        if (tag == null)
            return; //do nothing.

        if (mLayouted) {
            scrollToCard(tag);
        } else {
            //keep the tag for next use.
            mFirstVisibleCardTag = tag;
        }
    }

    /**
     * If this flag is set,
     * after finishing initial onLayout event, an initial animation which is defined in DefaultCardStreamAnimator is launched.
     */
    public void triggerShowInitialAnimation(){
        mShowInitialAnimation = true;
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    public void setCardStreamAnimator( CardStreamAnimator animators ){

        if( animators == null )
            mAnimators = new CardStreamAnimator.EmptyAnimator();
        else
            mAnimators = animators;

        LayoutTransition layoutTransition = getLayoutTransition();

        if( layoutTransition != null ){
            layoutTransition.setAnimator( LayoutTransition.APPEARING,
                    mAnimators.getAppearingAnimator(getContext()) );
            layoutTransition.setAnimator( LayoutTransition.DISAPPEARING,
                    mAnimators.getDisappearingAnimator(getContext()) );
        }
    }

    /**
     * set a OnDismissListener which called when user dismiss a card.
     *
     * @param listener
     */
    public void setOnDismissListener(OnDissmissListener listener) {
        mDismissListener = listener;
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private void initialize(AttributeSet attrs, int defStyle) {

        float speedFactor = 1.f;

        if (attrs != null) {
            TypedArray a = getContext().obtainStyledAttributes(attrs,
                    R.styleable.CardStream, defStyle, 0);

            if( a != null ){
                int speedType = a.getInt(R.styleable.CardStream_animationDuration, 1001);
                switch (speedType){
                    case ANIMATION_SPEED_FAST:
                        speedFactor = 0.5f;
                        break;
                    case ANIMATION_SPEED_NORMAL:
                        speedFactor = 1.f;
                        break;
                    case ANIMATION_SPEED_SLOW:
                        speedFactor = 2.f;
                        break;
                }

                String animatorName = a.getString(R.styleable.CardStream_animators);

                try {
                    if( animatorName != null )
                        mAnimators = (CardStreamAnimator) getClass().getClassLoader()
                                .loadClass(animatorName).newInstance();
                } catch (Exception e) {
                    Log.e(TAG, "Fail to load animator:" + animatorName, e);
                } finally {
                    if(mAnimators == null)
                        mAnimators = new DefaultCardStreamAnimator();
                }
                a.recycle();
            }
        }

        mAnimators.setSpeedFactor(speedFactor);
        mSwipeSlop = ViewConfiguration.get(getContext()).getScaledTouchSlop();
        setOnHierarchyChangeListener(mOnHierarchyChangeListener);
    }

    private void initCard(View cardView, boolean canDismiss) {
        resetAnimatedView(cardView);
        cardView.setOnTouchListener(mTouchListener);
        if (!canDismiss)
            mFixedViewList.add(cardView);
    }

    private boolean isFixedView(View v) {
        return mFixedViewList.contains(v);
    }

    private void resetAnimatedView(View child) {
        child.setAlpha(1.f);
        child.setTranslationX(0.f);
        child.setTranslationY(0.f);
        child.setRotation(0.f);
        child.setRotationY(0.f);
        child.setRotationX(0.f);
        child.setScaleX(1.f);
        child.setScaleY(1.f);
    }

    @TargetApi(Build.VERSION_CODES.HONEYCOMB)
    private void runInitialAnimations() {
        if( mAnimators == null )
            return;

        final int count = getChildCount();

        for (int index = 0; index < count; ++index) {
            final View child = getChildAt(index);
            ObjectAnimator animator =  mAnimators.getInitalAnimator(getContext());
            if( animator != null ){
                animator.setTarget(child);
                animator.start();
            }
        }
    }

    private void runShowActionAreaAnimation(View parent, View area) {
        area.setPivotY(0.f);
        area.setPivotX(parent.getWidth() / 2.f);

        area.setAlpha(0.5f);
        area.setRotationX(-90.f);
        area.animate().rotationX(0.f).alpha(1.f).setDuration(400);
    }

    private void handleViewSwipingOut(final View child, float deltaX, float deltaY) {
        ObjectAnimator animator = mAnimators.getSwipeOutAnimator(child, deltaX, deltaY);
        if( animator != null ){
            animator.addListener(new EndAnimationWrapper() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    removeView(child);
                    notifyOnDismissEvent(child);
                }
            });
        } else {
            removeView(child);
            notifyOnDismissEvent(child);
        }

        if( animator != null ){
            animator.setTarget(child);
            animator.start();
        }
    }

    private void handleViewSwipingIn(final View child, float deltaX, float deltaY) {
        ObjectAnimator animator = mAnimators.getSwipeInAnimator(child, deltaX, deltaY);
        if( animator != null ){
            animator.addListener(new EndAnimationWrapper() {
                @Override
                public void onAnimationEnd(Animator animation) {
                    child.setTranslationY(0.f);
                    child.setTranslationX(0.f);
                }
            });
        } else {
            child.setTranslationY(0.f);
            child.setTranslationX(0.f);
        }

        if( animator != null ){
            animator.setTarget(child);
            animator.start();
        }
    }

    private void scrollToCard(String tag) {

        <#-- TODO(chansuk): check orientation value. -->

        final int count = getChildCount();
        for (int index = 0; index < count; ++index) {
            View child = getChildAt(index);

            if (tag.equals(child.getTag())) {

                ViewParent parent = getParent();
                if( parent != null && parent instanceof ScrollView ){
                    ((ScrollView)parent).smoothScrollTo(
                            0, child.getTop() - getPaddingTop() - child.getPaddingTop());
                }
                return;
            }
        }
    }

    public interface OnDissmissListener {
        public void onDismiss(String tag);
    }

    /**
     * Empty default AnimationListener
     */
    private abstract class EndAnimationWrapper implements Animator.AnimatorListener {

        @Override
        public void onAnimationStart(Animator animation) {
        }

        @Override
        public void onAnimationCancel(Animator animation) {
        }

        @Override
        public void onAnimationRepeat(Animator animation) {
        }
    }//end of inner class
}
